home *** CD-ROM | disk | FTP | other *** search
/ Amiga Tools 2 / Amiga Tools 2.iso / tools / vim / src / csearch.c < prev    next >
C/C++ Source or Header  |  1995-03-09  |  14KB  |  582 lines

  1. /* vi:ts=4:sw=4
  2.  *
  3.  * VIM - Vi IMproved        by Bram Moolenaar
  4.  *
  5.  * Read the file "credits.txt" for a list of people who contributed.
  6.  * Read the file "uganda.txt" for copying and usage conditions.
  7.  */
  8.  
  9. /*
  10.  *
  11.  * csearch.c: dosub() and doglob() for :s, :g and :v
  12.  */
  13.  
  14. #include "vim.h"
  15. #include "globals.h"
  16. #include "proto.h"
  17. #include "param.h"
  18.  
  19. /* we use modified Henry Spencer's regular expression routines */
  20. #include "regexp.h"
  21.  
  22. /* dosub(lp, up, cmd)
  23.  *
  24.  * Perform a substitution from line 'lp' to line 'up' using the
  25.  * command pointed to by 'cmd' which should be of the form:
  26.  *
  27.  * /pattern/substitution/gc
  28.  *
  29.  * The trailing 'g' is optional and, if present, indicates that multiple
  30.  * substitutions should be performed on each line, if applicable.
  31.  * The trailing 'c' is optional and, if present, indicates that a confirmation
  32.  * will be asked for each replacement.
  33.  * The usual escapes are supported as described in the regexp docs.
  34.  *
  35.  * use_old == 0 for :substitute
  36.  * use_old == 1 for :&
  37.  * use_old == 2 for :~
  38.  */
  39.  
  40.     void
  41. dosub(lp, up, cmd, nextcommand, use_old)
  42.     linenr_t    lp;
  43.     linenr_t    up;
  44.     char_u        *cmd;
  45.     char_u        **nextcommand;
  46.     int            use_old;
  47. {
  48.     linenr_t        lnum;
  49.     long            i;
  50.     char_u           *ptr;
  51.     char_u           *old_line;
  52.     regexp           *prog;
  53.     long            nsubs = 0;
  54.     linenr_t        nlines = 0;
  55.     static int        do_all = FALSE;     /* do multiple substitutions per line */
  56.     static int        do_ask = FALSE;     /* ask for confirmation */
  57.     char_u           *pat = NULL, *sub = NULL;
  58.     static char_u   *old_sub = NULL;
  59.     int             delimiter;
  60.     int             sublen;
  61.     int                got_quit = FALSE;
  62.     int                got_match = FALSE;
  63.     int                temp;
  64.     int                which_pat;
  65.     
  66.     if (use_old == 2)
  67.         which_pat = 2;        /* use last used regexp */
  68.     else
  69.         which_pat = 1;        /* use last substitute regexp */
  70.  
  71.                                    /* new pattern and substitution */
  72.     if (use_old == 0 && *cmd != NUL && strchr("0123456789gcr|\"", *cmd) == NULL)
  73.     {
  74.         if (isalpha(*cmd))            /* don't accept alpha for separator */
  75.         {
  76.             emsg(e_invarg);
  77.             return;
  78.         }
  79.         /*
  80.          * undocumented vi feature:
  81.          *    "\/sub/" and "\?sub?" use last used search pattern (almost like //sub/r).
  82.          *  "\&sub&" use last substitute pattern (like //sub/).
  83.          */
  84.         if (*cmd == '\\')
  85.         {
  86.             ++cmd;
  87.             if (strchr("/?&", *cmd) == NULL)
  88.             {
  89.                 emsg(e_backslash);
  90.                 return;
  91.             }
  92.             if (*cmd != '&')
  93.                 which_pat = 0;                /* use last '/' pattern */
  94.             pat = (char_u *)"";                /* empty search pattern */
  95.             delimiter = *cmd++;                /* remember delimiter character */
  96.         }
  97.         else            /* find the end of the regexp */
  98.         {
  99.             delimiter = *cmd++;                /* remember delimiter character */
  100.             pat = cmd;                        /* remember start of search pattern */
  101.             cmd = skip_regexp(cmd, delimiter);
  102.             if (cmd[0] == delimiter)        /* end delimiter found */
  103.                 *cmd++ = NUL;                /* replace it by a NUL */
  104.         }
  105.  
  106.         /*
  107.          * Small incompatibility: vi sees '\n' as end of the command, but in
  108.          * Vim we want to use '\n' to find/substitute a NUL.
  109.          */
  110.         sub = cmd;            /* remember the start of the substitution */
  111.  
  112.         while (cmd[0])
  113.         {
  114.             if (cmd[0] == delimiter)            /* end delimiter found */
  115.             {
  116.                 *cmd++ = NUL;                    /* replace it by a NUL */
  117.                 break;
  118.             }
  119.             if (cmd[0] == '\\' && cmd[1] != 0)    /* skip escaped characters */
  120.                 ++cmd;
  121.             ++cmd;
  122.         }
  123.  
  124.         free(old_sub);
  125.         old_sub = strsave(sub);
  126.     }
  127.     else                                /* use previous pattern and substitution */
  128.     {
  129.         if (old_sub == NULL)    /* there is no previous command */
  130.         {
  131.             emsg(e_nopresub);
  132.             return;
  133.         }
  134.         pat = NULL;             /* myregcomp() will use previous pattern */
  135.         sub = old_sub;
  136.     }
  137.  
  138.     /*
  139.      * find trailing options
  140.      */
  141.     if (!p_ed)
  142.     {
  143.         if (p_gd)                /* default is global on */
  144.             do_all = TRUE;
  145.         else
  146.             do_all = FALSE;
  147.         do_ask = FALSE;
  148.     }
  149.     while (*cmd)
  150.     {
  151.         /*
  152.          * Note that 'g' and 'c' are always inverted, also when p_ed is off
  153.          * 'r' is never inverted.
  154.          */
  155.         if (*cmd == 'g')
  156.             do_all = !do_all;
  157.         else if (*cmd == 'c')
  158.             do_ask = !do_ask;
  159.         else if (*cmd == 'r')        /* use last used regexp */
  160.             which_pat = 2;
  161.         else
  162.             break;
  163.         ++cmd;
  164.     }
  165.  
  166.     /*
  167.      * check for a trailing count
  168.      */
  169.     skipspace(&cmd);
  170.     if (isdigit(*cmd))
  171.     {
  172.         i = getdigits(&cmd);
  173.         if (i <= 0)
  174.         {
  175.             emsg(e_zerocount);
  176.             return;
  177.         }
  178.         lp = up;
  179.         up += i - 1;
  180.     }
  181.  
  182.     /*
  183.      * check for trailing '|', '"' or '\n'
  184.      */
  185.     skipspace(&cmd);
  186.     if (*cmd)
  187.     {
  188.         if (strchr("|\"\n", *cmd) == NULL)
  189.         {
  190.             emsg(e_trailing);
  191.             return;
  192.         }
  193.         else
  194.             *nextcommand = cmd;
  195.     }
  196.  
  197.     if ((prog = myregcomp(pat, 1, which_pat)) == NULL)
  198.     {
  199.         emsg(e_invcmd);
  200.         return;
  201.     }
  202.  
  203.     /*
  204.      * ~ in the substitute pattern is replaced by the old pattern.
  205.      * We do it here once to avoid it to be replaced over and over again.
  206.      */
  207.     sub = regtilde(sub, (int)p_magic);
  208.  
  209.     old_line = NULL;
  210.     for (lnum = lp; lnum <= up && !(got_int || got_quit); ++lnum)
  211.     {
  212.         ptr = ml_get(lnum);
  213.         if (regexec(prog, ptr, TRUE))  /* a match on this line */
  214.         {
  215.             char_u        *new_end, *new_start = NULL;
  216.             char_u        *old_match, *old_copy;
  217.             char_u        *prev_old_match = NULL;
  218.             char_u        *p1;
  219.             int            did_sub = FALSE;
  220.             int            match, lastone;
  221.  
  222.             /* make a copy of the line, so it won't be taken away when updating
  223.                 the screen */
  224.             if ((old_line = strsave(ptr)) == NULL)
  225.                 continue;
  226.             regexec(prog, old_line, TRUE);  /* match again on this line to update the pointers. TODO: remove extra regexec() */
  227.             if (!got_match)
  228.             {
  229.                 setpcmark();
  230.                 got_match = TRUE;
  231.             }
  232.  
  233.             old_copy = old_match = old_line;
  234.             for (;;)            /* loop until nothing more to replace */
  235.             {
  236.                 /*
  237.                  * Save the position of the last change for the final cursor
  238.                  * position (just like the real vi).
  239.                  */
  240.                 curwin->w_cursor.lnum = lnum;
  241.                 curwin->w_cursor.col = (int)(prog->startp[0] - old_line);
  242.  
  243.                 /*
  244.                  * Match empty string does not count, except for first match.
  245.                  * This reproduces the strange vi behaviour.
  246.                  * This also catches endless loops.
  247.                  */
  248.                 if (old_match == prev_old_match && old_match == prog->endp[0])
  249.                 {
  250.                     ++old_match;
  251.                     goto skip;
  252.                 }
  253.                 old_match = prog->endp[0];
  254.                 prev_old_match = old_match;
  255.  
  256.                 while (do_ask)        /* loop until 'y', 'n' or 'q' typed */
  257.                 {
  258.                     temp = RedrawingDisabled;
  259.                     RedrawingDisabled = FALSE;
  260.                     comp_Botline(curwin);
  261.                     updateScreen(CURSUPD);
  262.                                     /* same highlighting as for wait_return */
  263.                     (void)set_highlight('r');
  264.                     msg_highlight = TRUE;
  265.                     smsg((char_u *)"replace by %s (y/n/q)?", sub);
  266.                     showruler(TRUE);
  267.                     setcursor();
  268.                     RedrawingDisabled = temp;
  269.                     if ((i = vgetc()) == 'q' || i == ESC || i == Ctrl('C'))
  270.                     {
  271.                         got_quit = TRUE;
  272.                         break;
  273.                     }
  274.                     else if (i == 'n')
  275.                         goto skip;
  276.                     else if (i == 'y')
  277.                         break;
  278.                 }
  279.                 if (got_quit)
  280.                     break;
  281.  
  282.                         /* get length of substitution part */
  283.                 sublen = regsub(prog, sub, old_line, 0, (int)p_magic);
  284.                 if (new_start == NULL)
  285.                 {
  286.                     /*
  287.                      * Get some space for a temporary buffer to do the substitution
  288.                      * into.
  289.                      */
  290.                     if ((new_start = alloc((unsigned)(STRLEN(old_line) + sublen + 5))) == NULL)
  291.                         goto outofmem;
  292.                     *new_start = NUL;
  293.                 }
  294.                 else
  295.                 {
  296.                     /*
  297.                      * extend the temporary buffer to do the substitution into.
  298.                      */
  299.                     if ((p1 = alloc((unsigned)(STRLEN(new_start) + STRLEN(old_copy) + sublen + 1))) == NULL)
  300.                         goto outofmem;
  301.                     STRCPY(p1, new_start);
  302.                     free(new_start);
  303.                     new_start = p1;
  304.                 }
  305.  
  306.                 for (new_end = new_start; *new_end; new_end++)
  307.                     ;
  308.                 /*
  309.                  * copy up to the part that matched
  310.                  */
  311.                 while (old_copy < prog->startp[0])
  312.                     *new_end++ = *old_copy++;
  313.  
  314.                 regsub(prog, sub, new_end, 1, (int)p_magic);
  315.                 nsubs++;
  316.                 did_sub = TRUE;
  317.  
  318.                 /*
  319.                  * Now the trick is to replace CTRL-Ms with a real line break.
  320.                  * This would make it impossible to insert CTRL-Ms in the text.
  321.                  * That is the way vi works. In Vim the line break can be
  322.                  * avoided by preceding the CTRL-M with a CTRL-V. Now you can't
  323.                  * precede a line break with a CTRL-V, big deal.
  324.                  */
  325.                 while ((p1 = STRCHR(new_end, CR)) != NULL)
  326.                 {
  327.                     if (p1 == new_end || p1[-1] != Ctrl('V'))
  328.                     {
  329.                         if (u_inssub(lnum))                /* prepare for undo */
  330.                         {
  331.                             *p1 = NUL;                    /* truncate up to the CR */
  332.                             mark_adjust(lnum, MAXLNUM, 1L);
  333.                             ml_append(lnum - 1, new_start, (colnr_t)(p1 - new_start + 1), FALSE);
  334.                             ++lnum;
  335.                             ++up;                    /* number of lines increases */
  336.                             STRCPY(new_start, p1 + 1);    /* copy the rest */
  337.                             new_end = new_start;
  338.                         }
  339.                     }
  340.                     else                            /* remove CTRL-V */
  341.                     {
  342.                         STRCPY(p1 - 1, p1);
  343.                         new_end = p1;
  344.                     }
  345.                 }
  346.  
  347.                 old_copy = prog->endp[0];    /* remember next character to be copied */
  348.                 /*
  349.                  * continue searching after the match
  350.                  * prevent endless loop with patterns that match empty strings,
  351.                  * e.g. :s/$/pat/g or :s/[a-z]* /(&)/g
  352.                  */
  353. skip:
  354.                 match = -1;
  355.                 lastone = (*old_match == NUL || got_int || got_quit || !do_all);
  356.                 if (lastone || do_ask || (match = regexec(prog, old_match, (int)FALSE)) == 0)
  357.                 {
  358.                     if (new_start)
  359.                     {
  360.                         /*
  361.                          * Copy the rest of the line, that didn't match.
  362.                          * Old_match has to be adjusted, we use the end of the line
  363.                          * as reference, because the substitute may have changed
  364.                          * the number of characters.
  365.                          */
  366.                         STRCAT(new_start, old_copy);
  367.                         i = old_line + STRLEN(old_line) - old_match;
  368.                         if (u_savesub(lnum))
  369.                             ml_replace(lnum, new_start, TRUE);
  370.  
  371.                         free(old_line);            /* free the temp buffer */
  372.                         old_line = new_start;
  373.                         new_start = NULL;
  374.                         old_match = old_line + STRLEN(old_line) - i;
  375.                         if (old_match < old_line)        /* safety check */
  376.                         {
  377.                             EMSG("dosub internal error: old_match < old_line");
  378.                             old_match = old_line;
  379.                         }
  380.                         old_copy = old_line;
  381.                     }
  382.                     if (match == -1 && !lastone)
  383.                         match = regexec(prog, old_match, (int)FALSE);
  384.                     if (match <= 0)        /* quit loop if there is no more match */
  385.                         break;
  386.                 }
  387.                     /* breakcheck is slow, don't call it too often */
  388.                 if ((nsubs & 15) == 0)
  389.                     breakcheck();
  390.  
  391.             }
  392.             if (did_sub)
  393.                 ++nlines;
  394.             free(old_line);        /* free the copy of the original line */
  395.             old_line = NULL;
  396.         }
  397.             /* breakcheck is slow, don't call it too often */
  398.         if ((lnum & 15) == 0)
  399.             breakcheck();
  400.     }
  401.  
  402. outofmem:
  403.     free(old_line);        /* may have to free an allocated copy of the line */
  404.     if (nsubs)
  405.     {
  406.         CHANGED;
  407.         updateScreen(CURSUPD); /* need this to update LineSizes */
  408.         beginline(TRUE);
  409.         if (nsubs > p_report)
  410.             smsg((char_u *)"%s%ld substitution%s on %ld line%s",
  411.                                 got_int ? "(Interrupted) " : "",
  412.                                 nsubs, plural(nsubs),
  413.                                 (long)nlines, plural((long)nlines));
  414.         else if (got_int)
  415.                 emsg(e_interr);
  416.         else if (do_ask)
  417.                 MSG("");
  418.     }
  419.     else if (got_int)        /* interrupted */
  420.         emsg(e_interr);
  421.     else if (got_match)        /* did find something but nothing substituted */
  422.         MSG("");
  423.     else                    /* nothing found */
  424.         emsg(e_nomatch);
  425.  
  426.     free(prog);
  427. }
  428.  
  429. /*
  430.  * doglob(cmd)
  431.  *
  432.  * Execute a global command of the form:
  433.  *
  434.  * g/pattern/X : execute X on all lines where pattern matches
  435.  * v/pattern/X : execute X on all lines where pattern does not match
  436.  *
  437.  * where 'X' is an EX command
  438.  *
  439.  * The command character (as well as the trailing slash) is optional, and
  440.  * is assumed to be 'p' if missing.
  441.  *
  442.  * This is implemented in two passes: first we scan the file for the pattern and
  443.  * set a mark for each line that (not) matches. secondly we execute the command
  444.  * for each line that has a mark. This is required because after deleting
  445.  * lines we do not know where to search for the next match.
  446.  */
  447.  
  448.     void
  449. doglob(type, lp, up, cmd)
  450.     int         type;
  451.     linenr_t    lp, up;
  452.     char_u        *cmd;
  453. {
  454.     linenr_t        lnum;        /* line number according to old situation */
  455.     linenr_t        old_lcount; /* curbuf->b_ml.ml_line_count before the command */
  456.     int             ndone;
  457.  
  458.     char_u            delim;        /* delimiter, normally '/' */
  459.     char_u           *pat;
  460.     regexp           *prog;
  461.     int                match;
  462.     int                which_pat;
  463.  
  464.     if (global_busy)
  465.     {
  466.         EMSG("Cannot do :global recursive");
  467.         ++global_busy;
  468.         return;
  469.     }
  470.  
  471.     which_pat = 2;            /* default: use last used regexp */
  472.  
  473.     /*
  474.      * undocumented vi feature:
  475.      *    "\/" and "\?": use previous search pattern.
  476.      *           "\&": use previous substitute pattern.
  477.      */
  478.     if (*cmd == '\\')
  479.     {
  480.         ++cmd;
  481.         if (strchr("/?&", *cmd) == NULL)
  482.         {
  483.             emsg(e_backslash);
  484.             return;
  485.         }
  486.         if (*cmd == '&')
  487.             which_pat = 1;        /* use previous substitute pattern */
  488.         else
  489.             which_pat = 0;        /* use previous search pattern */
  490.         ++cmd;
  491.         pat = (char_u *)"";
  492.     }
  493.     else
  494.     {
  495.         delim = *cmd;             /* get the delimiter */
  496.         if (delim)
  497.             ++cmd;                /* skip delimiter if there is one */
  498.         pat = cmd;                /* remember start of pattern */
  499.         cmd = skip_regexp(cmd, delim);
  500.         if (cmd[0] == delim)                /* end delimiter found */
  501.             *cmd++ = NUL;                    /* replace it by a NUL */
  502.     }
  503.  
  504.     reg_ic = p_ic;           /* set "ignore case" flag appropriately */
  505.  
  506.     if ((prog = myregcomp(pat, 2, which_pat)) == NULL)
  507.     {
  508.         emsg(e_invcmd);
  509.         return;
  510.     }
  511.     MSG("");
  512.  
  513. /*
  514.  * pass 1: set marks for each (not) matching line
  515.  */
  516.     ndone = 0;
  517.     for (lnum = lp; lnum <= up && !got_int; ++lnum)
  518.     {
  519.         match = regexec(prog, ml_get(lnum), (int)TRUE);     /* a match on this line? */
  520.         if ((type == 'g' && match) || (type == 'v' && !match))
  521.         {
  522.             ml_setmarked(lnum);
  523.             ndone++;
  524.         }
  525.             /* breakcheck is slow, don't call it too often */
  526.         if ((lnum & 15) == 0)
  527.             breakcheck();
  528.     }
  529.  
  530. /*
  531.  * pass 2: execute the command for each line that has been marked
  532.  */
  533.     if (got_int)
  534.         MSG("Interrupted");
  535.     else if (ndone == 0)
  536.         msg(e_nomatch);
  537.     else
  538.     {
  539.         global_busy = 1;
  540.         dont_sleep = 1;            /* don't sleep in emsg() */
  541.         no_wait_return = 1;        /* dont wait for return until finished */
  542.         need_wait_return = FALSE;
  543.         RedrawingDisabled = TRUE;
  544.         old_lcount = curbuf->b_ml.ml_line_count;
  545.         did_msg = FALSE;
  546.         while (!got_int && (lnum = ml_firstmarked()) != 0 && global_busy == 1)
  547.         {
  548.             /*
  549.              * If there was a message from the previous command, scroll
  550.              * the lines up for the next, otherwise it will be overwritten.
  551.              * did_msg is set by msg_start().
  552.              */
  553.             if (did_msg)
  554.             {
  555.                 cmdline_row = msg_row;
  556.                 did_msg = FALSE;
  557.             }
  558.             curwin->w_cursor.lnum = lnum;
  559.             curwin->w_cursor.col = 0;
  560.             if (*cmd == NUL || *cmd == '\n')
  561.                 docmdline((char_u *)"p");
  562.             else
  563.                 docmdline(cmd);
  564.             breakcheck();
  565.         }
  566.  
  567.         RedrawingDisabled = FALSE;
  568.         global_busy = 0;
  569.         dont_sleep = 0;
  570.         no_wait_return = 0;
  571.         if (need_wait_return)                /* wait for return now */
  572.             wait_return(FALSE);
  573.  
  574.         screenclear();
  575.         updateScreen(CURSUPD);
  576.         msgmore(curbuf->b_ml.ml_line_count - old_lcount);
  577.     }
  578.  
  579.     ml_clearmarked();      /* clear rest of the marks */
  580.     free(prog);
  581. }
  582.